#include <goldfish/head.h>
#include <goldfish/dma.h>
#include <xen/spinlock.h>
#include <xen/list.h>
#include <asm-arm/io.h>
#include <asm-arm/page.h>
#include <xen/mm.h>
#include <asm-arm/cpu-ops.h>
#include <asm-arm/pgtable.h>
#include <asm-arm/types.h>
/*
 * VM region handling support.
 *
 * This should become something generic, handling VM region allocations for
 * vmalloc and similar (ioremap, module space, etc).
 *
 * I envisage vmalloc()'s supporting vm_struct becoming:
 *
 *  struct vm_struct {
 *    struct vm_region	region;
 *    unsigned long	flags;
 *    struct page	**pages;
 *    unsigned int	nr_pages;
 *    unsigned long	phys_addr;
 *  };
 *
 * get_vm_area() would then call vm_region_alloc with an appropriate
 * struct vm_region head (eg):
 *
 *  struct vm_region vmalloc_head = {
 *	.vm_list	= LIST_HEAD_INIT(vmalloc_head.vm_list),
 *	.vm_start	= VMALLOC_START,
 *	.vm_end		= VMALLOC_END,
 *  };
 *
 * However, vmalloc_head.vm_start is variable (typically, it is dependent on
 * the amount of RAM found at boot time.)  I would imagine that get_vm_area()
 * would have to initialise this each time prior to calling vm_region_alloc().
 */
spinlock_t consistent_lock;
struct arm_vm_region {
	struct list_head	vm_list;
	unsigned long		vm_start;
	unsigned long		vm_end;
	struct page		*vm_pages;
	int			vm_active;
};

static struct arm_vm_region consistent_head = {
	.vm_list	= LIST_HEAD_INIT(consistent_head.vm_list),
	.vm_start	= CONSISTENT_BASE,
	.vm_end		= CONSISTENT_END,
};

static struct arm_vm_region *
arm_vm_region_alloc(struct arm_vm_region *head, size_t size, gfp_t gfp)
{
	unsigned long addr = head->vm_start, end = head->vm_end - size;
	unsigned long flags;
	struct arm_vm_region *c, *new;

	new = xmalloc(struct arm_vm_region);
	if (!new)
		goto out;

	spin_lock_irqsave(&consistent_lock, flags);

	list_for_each_entry(c, &head->vm_list, vm_list) {
		if ((addr + size) < addr)
			goto nospc;
		if ((addr + size) <= c->vm_start)
			goto found;
		addr = c->vm_end;
		if (addr > end)
			goto nospc;
	}

 found:
	/*
	 * Insert this entry _before_ the one we found.
	 */
	list_add_tail(&new->vm_list, &c->vm_list);
	new->vm_start = addr;
	new->vm_end = addr + size;
	new->vm_active = 1;

	spin_unlock_irqrestore(&consistent_lock, flags);
	return new;

 nospc:
	spin_unlock_irqrestore(&consistent_lock, flags);
	xfree(new);
 out:
	return NULL;
}

static void * __dma_alloc(/*struct device *dev,*/size_t size, dma_addr_t *handle/*, gfp_t gfp*/,pgprot_t prot)
{
	struct page_info *page;
	struct arm_vm_region *c;
	unsigned long order;
	//u32 mask = ISA_DMA_THRESHOLD, limit;
	unsigned long flags;
	if (!consistent_pte[0]) {
		printk(KERN_ERR "%s: not initialised\n", __func__);
		//modify dump_stack();
		return NULL;
	}
	//printk("framesize:%u\n",size);
	size = round_pgup(size);

/*	if (dev) {
		mask = dev->coherent_dma_mask;

		/*
		 * Sanity check the DMA mask - it must be non-zero, and
		 * must be able to be satisfied by a DMA allocation.
		 */
	/*	if (mask == 0) {
			dev_warn(dev, "coherent DMA mask is unset\n");
			goto no_page;
		}

		if ((~mask) & ISA_DMA_THRESHOLD) {
			dev_warn(dev, "coherent DMA mask %#llx is smaller "
				 "than system GFP_DMA mask %#llx\n",
				 mask, (unsigned long long)ISA_DMA_THRESHOLD);
			goto no_page;
		}
	}*/

	/*
	 * Sanity check the allocation size.
	 */
	//size = PAGE_ALIGN(size);
	/*limit = (mask + 1) & ~mask;*/
	if (/*(limit && size >= limit) ||*/
	    size >= (CONSISTENT_END - CONSISTENT_BASE)) {
		printk(KERN_WARNING "coherent allocation too big "
		       "(requested %#x)\n", size);
		goto no_page;
	}

	order = get_order_from_bytes(size);

	/*if (mask != 0xffffffff)
		gfp |= GFP_DMA;*/

	//page = alloc_pages(gfp, order);
	local_irq_save(flags);
	page = alloc_heap_pages(0/*MEMZONE_XEN MEMZONE_DMADOM*/,order);
	local_irq_restore(flags);
	//page = alloc_xenheap_pages(order);
	//printk("order:%u,page:%p,pagetovirt:%x\n",order,page,page_to_virt(page));
	if (!page)
		goto no_page;

	/*
	 * Invalidate any data that might be lurking in the
	 * kernel direct-mapped region for device DMA.
	 */
	/*{
		void *ptr = page_to_virt(page);//page_address(page);
		memset(ptr, 0, size);
		cpu_flush_dma_range((unsigned long)ptr,(unsigned long)(ptr + size));
		//dmac_flush_range(ptr, ptr + size);
		printk("cpu flush dma range\n");
		cpu_flush_cache_range(virt_to_phys(ptr), virt_to_phys(ptr) + size);
		//outer_flush_range(__pa(ptr), __pa(ptr) + size);
	}*/
	/*
	 * Allocate a virtual address in the consistent mapping region.
	 */
	c = arm_vm_region_alloc(&consistent_head, size,
			   /* gfp &*/ ~(__GFP_DMA | __GFP_HIGHMEM));
    printk("arm vm region alloc success\n");
	if (c) {
		pte_t *pte;
		struct page_info *end = page + (1 << order);
		int idx = CONSISTENT_PTE_INDEX(c->vm_start);
		u32 off = CONSISTENT_OFFSET(c->vm_start) & (PTRS_PER_PTE-1);

		//printk("idx:%u\n",idx);
		pte = consistent_pte[idx] + off;
		c->vm_pages = page;

		//split_page(page, order);

		/*
		 * Set the "dma handle"
		 */
		//convert page to physic addrress
		*handle = page_to_phys(page);//page_to_dma(/*dev,*/ page);

		do {
			//BUG_ON(!pte_none(*pte));

			/*
			 * x86 does not mark the pages reserved...
			 */
			//SetPageReserved(page);
			//modify_pte(pte,mk_pte(page, prot));
			//
			*pte = l1e_from_paddr(page_to_phys(page), prot);//mk_pte(page, prot);
			
			//set_pte_ext(pte, mk_pte(page, prot), 0);
			page++;
			pte++;
			off++;
			if (off >= PTRS_PER_PTE) {
				off = 0;
				cpu_flush_cache_page((unsigned long)consistent_pte[idx]);
				pte = consistent_pte[++idx];
			}
		} while (size -= PAGE_SIZE);
		cpu_flush_cache_page((unsigned long)consistent_pte[idx]);
		/*
		 * Free the otherwise unused pages.
		 */
		while (page < end) {
			free_heap_pages(0/*MEMZONE_DMADOM*/,page,order);
			//__free_page(page);
			page++;
		}
		return (void *)c->vm_start;
	}

	if (page)
		free_heap_pages(0/*MEMZONE_DMADOM*/,page,order);
		//__free_pages(page, order);
 no_page:
 	printk("No enough memory for DMA\n");
	*handle = ~0;
	return NULL;
}

pgprot_t pgprot_kernel;
/*
 * Allocate a writecombining region, in much the same way as
 * dma_alloc_coherent above.
 */
void *
dma_alloc_writecombine(/*struct device *dev, */size_t size, dma_addr_t *handle/*, gfp_t gfp*/)
{
	return __dma_alloc(/*dev, */size, handle, /*gfp,*/
			   pgprot_writecombine(pgprot_kernel));
}


/*
 * Initialise the consistent memory allocation.
 */
int consistent_init(void)
{
	pde_t *pgd;
	//pmd_t *pmd;
	pte_t *pte;
	void *pt;
	int ret = 0, i = 0;
	u32 base = CONSISTENT_BASE;
	pgprot_kernel = PTE_TYPE_SMALL/* | PTE_BUFFERABLE | PTE_CACHEABLE*/ | PTE_SMALL_AP_UNO_SRW;//L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY | L_PTE_WRITE | L_PTE_EXEC;

	do {
		//pgd = idle_pg_table + pgd_index(base);pgd_offset(&init_mm, base);
		/*pmd = pmd_alloc(&init_mm, pgd, base);
		if (!pmd) {
			printk(KERN_ERR "%s: no pmd tables\n", __func__);
			ret = -ENOMEM;
			break;
		}*/
		//WARN_ON(!pmd_none(*pmd));l2_linear_offset(base)
		pt = alloc_xenheap_page();//pte_alloc_kernel(pgd, base);
		clear_page(pt);
		//printk("beforecpu_flush cache page\n");
		idle_pg_table[pgd_index(base)] =l2e_from_page(virt_to_page(pt), PMD_TYPE_TABLE | PMD_DOMAIN(/*DOMAIN_HYPERVISOR*/ DOMAIN_IO));
		//printk("l2e:%x,phys:%x\n",l2e_from_page(virt_to_page(pt), PMD_TYPE_TABLE | PMD_DOMAIN(DOMAIN_IO)),l2e_from_paddr(virt_to_phys(pt), PMD_TYPE_TABLE | PMD_DOMAIN(DOMAIN_IO)));
		cpu_flush_cache_page((unsigned long) &idle_pg_table[pgd_index(base)]);
		if (!pt) {
			printk(KERN_ERR "%s: no pte tables\n", __func__);
			ret = -ENOMEM;
			break;
		}
		//cpu_flush_cache_page((unsigned long) &idle_pg_table[pgd_index(base)]);
		consistent_pte[i++] = pt;
		base += (1 << PGDIR_SHIFT);
	} while (base < CONSISTENT_END);
	printk("consistent init ok\n");

	return ret;
}

/*void dma_grab(void)
{
	pde_t *pgd;
        pte_t *pte;
        void *pt;
        int ret = 0, i = 0;	
	do {
                //printk("l2e:%x,phys:%x\n",l2e_from_page(virt_to_page(pt), PMD_TYPE_TABLE | PMD_DOMAIN(DOMAIN_IO)),l2e_from_paddr(virt_to_phys(pt), PMD_TYPE_TABLE | PMD_DOMAIN(DOMAIN_IO)));
                cpu_flush_cache_page((unsigned long) &idle_pg_table[pgd_index(base)]);
                if (!pt) {
                        printk(KERN_ERR "%s: no pte tables\n", __func__);
                        ret = -ENOMEM;
                        break;
                }
                //cpu_flush_cache_page((unsigned long) &idle_pg_table[pgd_index(base)]);
                base += (1 << PGDIR_SHIFT);
        } while (base < CONSISTENT_END);
}*/

asmlinkage int asm_do_DMAscreen(void)
{
	//unsigned int data_adr = current->arch.guest_context.cpu_sys_regs_t.vfar;
	return 0;
}
